#ifdef HAVE_CONFIG_H
#   include "config.h"
#endif

#ifdef CEGUI_SAMPLES_USE_DIRECTX_10

#include "CEGuiD3D10BaseApplication.h"
#include "RendererModules/Direct3D10/CEGUIDirect3D10Renderer.h"
#include "CEGuiSample.h"
#include "Win32AppHelper.h"
#include "CEGUI.h"
#include "CEGUIRenderingRoot.h"

#include <stdexcept>
#include <d3d10.h>
#include <D3DX10math.h>
#include <dinput.h>

//----------------------------------------------------------------------------//

HRESULT CEGuiD3D10BaseApplication::createDeviceAndSwapChain()
{
    RECT rc;
    GetClientRect( pimpl->d_window, &rc );
    UINT width = rc.right - rc.left;
    UINT height = rc.bottom - rc.top;

    DXGI_SWAP_CHAIN_DESC scd;
    ZeroMemory(&scd, sizeof(scd));
    // init sqap chain descriptor structure
    scd.BufferCount = 1;
    scd.BufferDesc.Width = width;
    scd.BufferDesc.Height = height;
    scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    scd.BufferDesc.RefreshRate.Numerator = 60;
    scd.BufferDesc.RefreshRate.Denominator = 1;
    scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    scd.OutputWindow = pimpl->d_window;
    scd.SampleDesc.Count = 1;
    scd.SampleDesc.Quality = 0;
    scd.Windowed = true;

    // initialise main parts of D3D
    return D3D10CreateDeviceAndSwapChain(0, D3D10_DRIVER_TYPE_HARDWARE,
        0, 0, D3D10_SDK_VERSION,
        &scd, &pimpl->d_swapChain,
        &pimpl->d_device);
}

HRESULT CEGuiD3D10BaseApplication::createBackAndDepthBuffers()
{
    HRESULT hr = S_OK;

    RECT rc;
    GetClientRect( pimpl->d_window, &rc );
    UINT width = rc.right - rc.left;
    UINT height = rc.bottom - rc.top;

    // Create a render target view
    ID3D10Texture2D* back_buffer;
    hr = pimpl->d_swapChain->GetBuffer(0, __uuidof(ID3D10Texture2D),
        (LPVOID*)&back_buffer);

    if (FAILED(hr))
        return S_FALSE;


    ID3D10RenderTargetView* rtview;

    // create render target view using the back buffer
    hr = pimpl->d_device->
        CreateRenderTargetView(back_buffer, 0, &rtview);

    // release handle to buffer - we have done all we needed to with it.
    back_buffer->Release();

    // Create depth stencil texture
    D3D10_TEXTURE2D_DESC descDepth;
    descDepth.Width = width;
    descDepth.Height = height;
    descDepth.MipLevels = 1;
    descDepth.ArraySize = 1;
    descDepth.Format = DXGI_FORMAT_D32_FLOAT;
    descDepth.SampleDesc.Count = 1;
    descDepth.SampleDesc.Quality = 0;
    descDepth.Usage = D3D10_USAGE_DEFAULT;
    descDepth.BindFlags = D3D10_BIND_DEPTH_STENCIL;
    descDepth.CPUAccessFlags = 0;
    descDepth.MiscFlags = 0;
    hr = pimpl->d_device->CreateTexture2D( &descDepth, NULL, &depthBuffer );
    if( FAILED( hr ) )
        return hr;

    // Create the depth stencil view
    D3D10_DEPTH_STENCIL_VIEW_DESC descDSV;
    descDSV.Format = descDepth.Format;
    descDSV.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D;
    descDSV.Texture2D.MipSlice = 0;
    hr = pimpl->d_device->CreateDepthStencilView( depthBuffer, &descDSV, &depthBufferDSV );
    if( FAILED( hr ) )
    {
        rtview->Release();
        return hr;
    }

    // bind the back-buffer render target to get the output.
    pimpl->d_device->
        OMSetRenderTargets(1, &rtview, depthBufferDSV);

    // set a basic viewport.
    D3D10_VIEWPORT view_port;
    view_port.Width    = width;
    view_port.Height   = height;
    view_port.MinDepth = 0.0f;
    view_port.MaxDepth = 1.0f;
    view_port.TopLeftX = 0;
    view_port.TopLeftY = 0;
    pimpl->d_device->RSSetViewports(1, &view_port);

    if (FAILED(hr))
        return hr;

    // complete window initialization
    ShowWindow(pimpl->d_window, SW_NORMAL);
    UpdateWindow(pimpl->d_window);

    return hr;
}


HRESULT CEGuiD3D10BaseApplication::initD3DDevice()
{
    HRESULT hr = S_OK;


    if (FAILED(hr = createDeviceAndSwapChain()))
    {
        ErrorMsg("Failed to create dx device and swap chain.");
    }

    if (FAILED(hr = createBackAndDepthBuffers()))
    {
        ErrorMsg("Failed to initially create depth and back buffers.");
        pimpl->d_swapChain->Release();
        pimpl->d_device->Release();
        pimpl->d_swapChain = 0;
        pimpl->d_device = 0;
    }
    
    //if (SUCCEEDED(hr))
    //{
    //    hr = createScene();
    //}
    //else
    //{
    //    MessageBox(0, L"Failed to correctly initialise Direct3D 10",
    //        Win32AppHelper::APPLICATION_NAME, MB_ICONERROR|MB_OK);
    //}


    return false;

}

//----------------------------------------------------------------------------//
CEGuiD3D10BaseApplication::CEGuiD3D10BaseApplication() :
pimpl(new CEGuiBaseApplicationImpl),
d_lastFrameTime(GetTickCount()),
d_fps_lastTime(d_lastFrameTime),
d_fps_frames(0),
d_fps_value(0)
{
}

void CEGuiD3D10BaseApplication::setup()
{
    createWindow();

    //createCamera();

    createD3DDevice();

    //create renderer
    //bvRenderer = new BvD3D10Renderer(pimpl->d_device);

    // set the swap chain ptr into window data so we can get access
    // later.  This is a bit of a hack, but saved us redesigning the
    // entire framework just for this.
    SetWindowLongPtr(pimpl->d_window,
        GWLP_USERDATA,
        (LONG_PTR)pimpl->d_swapChain);

    if (Win32AppHelper::initialiseDirectInput(pimpl->d_window,
        pimpl->d_directInput))
    {
        pimpl->d_renderer =
            &CEGUI::Direct3D10Renderer::bootstrapSystem(pimpl->d_device);

        initialiseResourceGroupDirectories();
        initialiseDefaultResourceGroups();

        CEGUI::Logger::getSingleton().setLoggingLevel(CEGUI::Informative);

        // setup required to do direct rendering of FPS value
        const CEGUI::Rect scrn(CEGUI::Vector2(0, 0), 
            pimpl->d_renderer->getDisplaySize());
        d_fps_geometry = &pimpl->d_renderer->createGeometryBuffer();
        d_fps_geometry->setClippingRegion(scrn);

        // setup for logo
        CEGUI::ImagesetManager::getSingleton().
            createFromImageFile("cegui_logo", "logo.png", "imagesets");
        d_logo_geometry = &pimpl->d_renderer->createGeometryBuffer();
        d_logo_geometry->setClippingRegion(scrn);
        d_logo_geometry->setPivot(CEGUI::Vector3(50, 34.75f, 0));
        d_logo_geometry->setTranslation(CEGUI::Vector3(10, 520, 0));
        CEGUI::ImagesetManager::getSingleton().get("cegui_logo").
            getImage("full_image").draw(*d_logo_geometry, 
            CEGUI::Rect(0, 0, 200, 69.5f), 0);

        // clearing this queue actually makes sure it's created(!)
        pimpl->d_renderer->getDefaultRenderingRoot().clearGeometry(
            CEGUI::RQ_OVERLAY);

        // subscribe handler to render overlay items
        pimpl->d_renderer->getDefaultRenderingRoot().
            subscribeEvent(CEGUI::RenderingSurface::EventRenderQueueStarted,
            CEGUI::Event::Subscriber(
            &CEGuiD3D10BaseApplication::overlayHandler,this));
        return;
    }

    cleanupDirect3D();

    DestroyWindow(pimpl->d_window);
}

//----------------------------------------------------------------------------//
CEGuiD3D10BaseApplication::~CEGuiD3D10BaseApplication()
{
    Win32AppHelper::mouseLeaves();

    // cleanup gui system
    CEGUI::Direct3D10Renderer::destroySystem();

    Win32AppHelper::cleanupDirectInput(pimpl->d_directInput);

    cleanupDirect3D();

    DestroyWindow(pimpl->d_window);

    delete pimpl;
}

//----------------------------------------------------------------------------//
bool CEGuiD3D10BaseApplication::overlayHandler(const CEGUI::EventArgs& args)
{
    using namespace CEGUI;

    if (static_cast<const RenderQueueEventArgs&>(args).queueID != RQ_OVERLAY)
        return false;

    // render FPS:
    Font* fnt = System::getSingleton().getDefaultFont();
    if (fnt)
    {
        d_fps_geometry->reset();
        fnt->drawText(*d_fps_geometry, d_fps_textbuff, Vector2(0, 0), 0,
            colour(0xFFFFFFFF));
        d_fps_geometry->draw();
    }

    //d_logo_geometry->draw();

    return true;
}

//----------------------------------------------------------------------------//
bool CEGuiD3D10BaseApplication::execute(CEGuiSample* sampleApp)
{
    setup();

    //create scene and gui
    sampleApp->initialiseSample();

    float clear_colour[4] = { 0.0f, 0.125f, 0.3f, 1.0f };

    //
    //  This is basically a modified Win32 message pump
    //
    bool idle;

    while (Win32AppHelper::doWin32Events(idle))
    {
        if (idle)
        {
            CEGUI::System& guiSystem = CEGUI::System::getSingleton();

            // do time based updates
            DWORD thisTime = GetTickCount();
            float elapsed = static_cast<float>(thisTime - d_lastFrameTime);
            d_lastFrameTime = thisTime;
            // inject the time pulse
            guiSystem.injectTimePulse(elapsed / 1000.0f);

            doFPSUpdate();

            // update logo rotation
            static float rot = 0.0f;
            d_logo_geometry->setRotation(CEGUI::Vector3(rot, 0, 0));
            rot += 180.0f * (elapsed / 1000.0f);
            if (rot > 360.0f)
                rot -= 360.0f;

            Win32AppHelper::doDirectInputEvents(pimpl->d_directInput);

            // get render target view
            // this is a bit wasteful, but done like this for now since the
            // resize code can change the view from under us.
            ID3D10RenderTargetView* rtview;
            pimpl->d_device->OMGetRenderTargets(1, &rtview, 0);

            // clear display
            pimpl->d_device->ClearRenderTargetView(rtview, clear_colour);
            pimpl->d_device->ClearDepthStencilView( depthBufferDSV, D3D10_CLEAR_DEPTH, 1.0f, 0 );
            pimpl->d_device->OMSetRenderTargets(1, &rtview, depthBufferDSV);

            //draw scene
            sampleApp->drawPerFrame();

            // main CEGUI rendering call
            guiSystem.renderGUI();

            pimpl->d_swapChain->Present(0, 0);
            rtview->Release();
        }

        // check if the application is quitting, and break the loop next time
        // around if so.
        if (isQuitting())
            PostQuitMessage(0);
    }

    return true;
}

//----------------------------------------------------------------------------//
void CEGuiD3D10BaseApplication::cleanup()
{
    // nothing to do here.
}

//----------------------------------------------------------------------------//
void CEGuiD3D10BaseApplication::doFPSUpdate(void)
{
    ++d_fps_frames;

    DWORD thisTime = GetTickCount();

    if (thisTime - d_fps_lastTime >= 1000)
    {
        // update FPS text to output
        sprintf(d_fps_textbuff , "FPS: %d", d_fps_frames);
        d_fps_value = d_fps_frames;
        d_fps_frames = 0;
        d_fps_lastTime = thisTime;
    }

}

//----------------------------------------------------------------------------//
void CEGuiD3D10BaseApplication::cleanupDirect3D()
{
    if (pimpl->d_device)
    {
        // get render target view
        ID3D10RenderTargetView* rtview;
        pimpl->d_device->OMGetRenderTargets(1, &rtview, 0);

        if (rtview)
        {
            // we release once for the reference we just asked for
            rtview->Release();
            // we release again for the original reference made at creation.
            rtview->Release();
        }

        pimpl->d_swapChain->Release();
        pimpl->d_device->Release();

        pimpl->d_swapChain = 0;
        pimpl->d_device = 0;
    }
}

////----------------------------------------------------------------------------//
//void CEGuiD3D10BaseApplication::createCamera()
//{
//    m_camera = new BvCamera;
//}
//
////----------------------------------------------------------------------------//
//HRESULT CEGuiD3D10BaseApplication::createScene()
//{
//
//    return S_OK;
//}
//
////----------------------------------------------------------------------------//
//void CEGuiD3D10BaseApplication::drawPerFrame()
//{
//
//}

//----------------------------------------------------------------------------//
void CEGuiD3D10BaseApplication::createWindow()
{
    if (FAILED(pimpl->d_window = Win32AppHelper::createApplicationWindow(800, 600)))
    {
        MessageBox(0, Win32AppHelper::CREATE_WINDOW_ERROR,
            Win32AppHelper::APPLICATION_NAME, MB_ICONERROR|MB_OK);

        CEGUI_THROW(std::runtime_error("Windows Direct3D 10 application failed to "
            "initialise."));
    }
}

//----------------------------------------------------------------------------//
void CEGuiD3D10BaseApplication::createD3DDevice()
{
    if (FAILED(initD3DDevice()))
    {
        cleanupDirect3D();
        ErrorMsg("Windows Direct3D 10 Device failed to create.");
        CEGUI_THROW(std::runtime_error("Windows Direct3D 10 Device failed to create."));
    }
}

#endif
